package com.xyrl.project.common.authentication;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.xyrl.common.base.exception.OtherLoginException;
import com.xyrl.common.base.exception.TokenTimeOutException;
import com.xyrl.common.util.ObjectUtil;
import com.xyrl.common.util.RedisUtil;
import com.xyrl.common.util.StringUtil;
import com.xyrl.project.common.constant.XyrlConstant;
import com.xyrl.project.common.util.XyrlUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;

import java.util.concurrent.TimeUnit;

@Slf4j
public class JWTUtil {
    /**
     * 系统配置超时时间
     */
//    private static final long EXPIRE_TIME = SpringContextUtil.getBean(XyrlProperties.class).getShiro().getJwtTimeOut();
    private static final long EXPIRE_TIME = 60 * 60;

    /**
     * 校验 token是否正确
     *
     * @param token  密钥
     * @param secret 用户的密码
     * @return 是否正确
     */
    public static boolean verify(String token, LoginInfo info, String secret) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("xyrlUser", JSON.toJSONString(info))
                    .build();
            verifier.verify(token);
            log.info("token is valid");
            return true;
        } catch (Exception e) {
            log.info("token is invalid{}", e.getMessage());
            return false;
        }
    }


    /**
     * 从 token中获取用户名
     *
     * @return token中包含的用户名
     */
    public static String getUsername(String token) {
        LoginInfo info = getLoginInfo(token);
        if (ObjectUtil.isEmpty(info)) {
            return null;
        }
        return info.getLoginName();
    }

    /**
     * 取得登录用户
     *
     * @param token jwt 生成的token
     * @return
     */
    public static LoginInfo getLoginInfo(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            String info = jwt.getClaim("xyrlUser").asString();
            LoginInfo login = JSONObject.parseObject(info, LoginInfo.class);
            return login;
        } catch (JWTDecodeException e) {
            log.error("error：{}", e.getMessage());
            return null;
        }
    }

    /**
     * 生成 token
     *
     * @param info   登录用户信息（登录帐号 用户类型）
     * @param secret 用户的密码
     * @return token
     */
    public static String sign(LoginInfo info, String secret) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            return JWT.create()
                    .withClaim("xyrlUser", JSON.toJSONString(info))
                    .sign(algorithm);
        } catch (Exception e) {
            log.error("error：{}", e);
            return null;
        }
    }


    /**
     * 刷新token 过期时间
     *
     * @param token
     * @author zhongp
     * @date 2019-12-30 16:46:03
     */
    public static void refreshTokenExpire(String token) {
        RedisUtil.setExpireTime(StringUtil.concat(XyrlConstant.TOKEN_CACHE_PREFIX, token), EXPIRE_TIME);
        //取得登录用户信息
        LoginInfo info = getLoginInfo(XyrlUtil.decryptToken(token));
        //刷新在线用户过期时间
        if (ObjectUtil.isNotEmpty(info)) {
            //缓存在线用户token
            String cacheUserKey = StringUtil.concat(info.getUserType(), ":", info.getLoginName());
            //超时时间
            long expireTime = System.currentTimeMillis() / 1000 + EXPIRE_TIME;
            // zset 存储登录用户，score 为过期时间戳
            RedisUtil.setSortSet(XyrlConstant.ACTIVE_USERS_ZSET_PREFIX, cacheUserKey, expireTime);
        }
    }

    /**
     * 缓存数据
     *
     * @param token
     * @author zhongp
     * @date 2019-12-30 16:46:03
     */
    public static void cacheToken(String token, String value) {
        RedisUtil.setData(StringUtil.concat(XyrlConstant.TOKEN_CACHE_PREFIX, token), value, EXPIRE_TIME, TimeUnit.SECONDS);
    }

    /**
     * 缓存数据
     *
     * @param token
     * @author zhongp
     * @date 2019-12-30 16:46:03
     */
    public static void cacheToken(String token) {
        cacheToken(token, token);
    }

    /**
     * 刷新token 过期时间
     *
     * @param token
     * @author zhongp
     * @date 2019-12-30 16:46:03
     */
    public static void clearToken(String token) {
        RedisUtil.delKey(StringUtil.concat(XyrlConstant.TOKEN_CACHE_PREFIX, token));
    }

    /**
     * 判断token 是否有效
     *
     * @param token
     * @author zhongp
     * @date 2019-12-30 16:46:12
     */
    public static boolean isTimeOut(String token) {
        String time = RedisUtil.getData(StringUtil.concat(XyrlConstant.TOKEN_CACHE_PREFIX, token));
        if (ObjectUtil.isEmpty(time)) {
            return true;
        }
        refreshTokenExpire(token);
        return false;
    }

    /**
     * 校验token 的有效性  包括 token 的有效期 是否他处登录 token 的有效性
     *
     * @param token 当前token
     */
    public static LoginInfo checkToken(String token) throws AuthenticationException {
        String encryptToken = XyrlUtil.encryptToken(token);
        //判断是否存在token
        if (JWTUtil.isTimeOut(encryptToken)) {
            throw new TokenTimeOutException("登录己过期");
        }
        //判断是否在异地登录
        LoginInfo loginInfo = getLoginInfo(token);
        if (ObjectUtil.isEmpty(loginInfo)) {
            throw new TokenTimeOutException("token校验不通过");
        }
        String onlineUser = StringUtil.concat(loginInfo.getUserType(), ":", loginInfo.getLoginName());
        //判断token 是否失效
        Object activeToken = RedisUtil.getMap(XyrlConstant.ACTIVE_USER_TOKEN, onlineUser);
        if (ObjectUtil.isNotEmpty(activeToken)) {
            if (!encryptToken.equalsIgnoreCase(activeToken.toString())) {
                throw new OtherLoginException("帐号己在其它设备登录。请重新登录");
            }
        }
        return loginInfo;
    }


    public static void main(String[] args) {
////        String deviceId= RandomStringUtils.randomAlphanumeric(20);
//        LoginInfo info = new LoginInfo("mrbird", XyrlConstant.USER_TYPE_SYSTEM);
//        String token1 = sign(info, "1234567");
//        System.out.println(token1);
////        System.out.println(XyrlUtil.encryptToken(token1));
////        System.out.println(token);
//////        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ4eXJsVXNlciI6IntcImxvZ2luTmFtZVwiOlwienBhbWFuXCIsXCJ1c2VyVHlwZVwiOlwic3lzdGVtXCJ9IiwiZXhwIjoxNTc3NjkyNDQwfQ.KoM0_2T1vxpj7dr4A8obfcHJeoIJBKN3rDOPCsCqNpM";
////        System.out.println(getLoginInfo(token));
////        System.out.println(verify(token, info, "1234567"));
//
////        Set<String> sets = new HashSet<>();
////        for(int i=0;i<1000000;i++){
////            sets.add(RandomStringUtils.randomAlphanumeric(20));
////        }
////        System.out.println(sets.size());
////        String token = "b9752a7c24e143196871a26491159bee4a42bcc8fd2a155dedfa8077557adb77ebbdd7894da29907bd54b3261c0ce2173400b238d73faf8cc1bffae1861dd0d89dda33fc8c973868c957bc2073f44bebd8feedc0b7eb8bc4e557f628b2a6df268b706067bf06b0288b2ae6fa3a03122ef0e243068b6b708ab61623a9f85b6149240b95da8e826894de1db341b69a7c2382e566b67ff2f28437f300b2765d9e064f011b2d643d2cf176a66069488c76e93ac031a2495c75d84db37ab8023ed3f54bccc81c0ee4a061e63c4d484ef523495d7e40cbb78f0a31a926129f4748107c";
////        String encryptToken = XyrlUtil.decryptToken(token);
//        System.out.println(getLoginInfo(token1));
        String token = "b9752a7c24e143196871a26491159bee4a42bcc8fd2a155dedfa8077557adb77ebbdd7894da29907bd54b3261c0ce2173400b238d73faf8cc1bffae1861dd0d89dda33fc8c973868b8a52c438f749100a6ee2509ef19a5978f0c2227af17a982de4ec25a76ac61118b2ae6fa3a03122ef0e243068b6b708ab61623a9f85b6149240b95da8e826894de1db341b69a7c2382e566b67ff2f28437f300b2765d9e064f011b2d643d2cf176a66069488c76e993fb138edfddb764692f05f5eae906770d4619b30fb5c0a255025e7f3b8e8e0cb89c1127a84a8c5b33c75a7ed066ca18";
        String encryptToken = XyrlUtil.decryptToken(token);
        System.out.println(encryptToken);
//        System.out.println(getLoginInfo(token1));


//        checkToken(token);
    }
}
